﻿using SAIM.Models.DAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;

namespace SAIM.Models.BLL
{
    public class TicketsBLL : IDisposable
    {
        private SAIMEntities db;
        private List<TicketsDAL> _value;

        /// <summary>
        /// DAL Tickets. It contains ticket information.
        /// </summary>
        public List<TicketsDAL> Value 
        {
            get { return (_value = _value ?? db.Tickets.ToList()); }
            set { _value = value; }
        }

        /// <summary>
        /// It represents a User Class for BLL. Interacts with the database and DAL to get the mapped information.
        /// </summary>
        public TicketsBLL()
        {
            db = new SAIMEntities();
        }

        /// <summary>
        /// Gets a Ticket by Id
        /// </summary>
        /// <param name="ticketId"></param>
        /// <returns></returns>
        public TicketsDAL Get(long TicketId)
        {
            return Value.FirstOrDefault(a => a.Id == TicketId);
        }

        /// <summary>
        /// Gets a Ticket by Id and User Id
        /// </summary>
        /// <param name="TicketId"></param>
        /// <param name="UserId"></param>
        /// <returns></returns>
        public TicketsDAL Get(long TicketId, string UserId)
        {
            return Value.FirstOrDefault(a => (a.Id == TicketId) && 
            ((a.UserId == UserId) || (a.UsersByTicket.FirstOrDefault(u => u.Id == UserId) != null)));
        }

        /// <summary>
        /// Crear un nuevo ticket
        /// </summary>
        /// <param name="_UserId">ID de usuario solicitante</param>
        /// <param name="_IssueId">ID de Issue</param>
        /// <param name="_TicketDate">Fecha de creación del ticket</param>
        /// <param name="_Details">Detalles del ticket</param>
        /// <param name="_TicketSubject">Servicio del ticket</param>
        /// <returns></returns>
        public long Insert(string _UserId, long _IssueId, DateTime _TicketDate, string _Details, string _TicketSubject)
        {
            TicketsDAL lastTicket = Value.OrderByDescending(t => t.Id).FirstOrDefault();
            TicketsDAL ticket = new TicketsDAL() // Inicialización del ticket
            {
                UserId = _UserId,
                IssueId = _IssueId,
                TicketDate = _TicketDate,
                Details = _Details,
                TicketSubject = _TicketSubject,
                TicketStatus = "En espera de atención",
                Id = lastTicket != null ? lastTicket.Id + 1 : 1
            };
            // Insercción del ticket en la base de datos
            try
            {
                using (AspNetUsersBLL userBLL = new AspNetUsersBLL(_UserId))
                {
                    // Si el usuario no cuenta con el perfil necesario para crear tickets se le asigna
                    if (!userBLL.isInRole("Create tickets"))
                    {
                        if (userBLL.SetRole("Create tickets"))
                        {
                            db.Tickets.Add(ticket);
                            db.SaveChanges();
                            return ticket.Id;
                        }
                        else
                            return -1;
                    }
                    else
                    {
                        db.Tickets.Add(ticket);
                        db.SaveChanges();
                        return ticket.Id;
                    }
                }
            }
            catch (Exception) { return -1; }
        }

        public List<TicketsDAL> getUserTickets(string userId)
        {
            return Value.FindAll(a => (a.UserId == userId) || (a.UsersByTicket.FirstOrDefault(u => u.Id == userId) != null)).OrderBy(a => a.TicketDate).ToList();
        }

        public List<TicketsDAL> GetTickets(string userId, int current, int rowCount, Dictionary<object, string> sort, string searchPhrase, DateTime init, DateTime end, string status, ref int total)
        {
            List<TicketsDAL> ticketsFound;

            switch (sort.Keys.First().ToString())
            {
                case "Id":
                    ticketsFound = sort["Id"] == "asc" ?
                        Value.OrderBy(a => a.Id).ToList() :
                        Value.OrderBy(a => a.Id).Reverse().ToList();
                    break;
                case "issue":
                    ticketsFound = sort["issue"] == "asc" ? 
                        Value.OrderBy(a => a.Issues.Name).ToList() : 
                        Value.OrderBy(a => a.Issues.Name).Reverse().ToList();
                    break;
                case "ticketSubject":
                    ticketsFound = sort["ticketSubject"] == "asc" ? 
                        Value.OrderBy(a => a.TicketSubject).ToList() :
                        Value.OrderBy(a => a.TicketSubject).Reverse().ToList();
                    break;
                case "userName":
                    ticketsFound = sort["userName"] == "asc" ? 
                        Value.OrderBy(a => a.AspNetUsers.UserName).ToList() :
                        Value.OrderBy(a => a.AspNetUsers.UserName).Reverse().ToList();
                    break;
                case "ticketDate":
                    ticketsFound = sort["ticketDate"] == "asc" ? 
                        Value.OrderBy(a => a.TicketDate).ToList() :
                        Value.OrderBy(a => a.TicketDate).Reverse().ToList();
                    break;
                case "area":
                    ticketsFound = sort["area"] == "asc" ?
                        Value.OrderBy(a => a.Issues.ParentIssue.Name).ToList() :
                        Value.OrderBy(a => a.Issues.ParentIssue.Name).Reverse().ToList();
                    break;
                default:
                    ticketsFound = Value.OrderBy(a => a.TicketDate).Reverse().ToList();
                    break;

            }
            // Filtro mediante el rango de fechas y la palbra de busqueda
            ticketsFound = ticketsFound.FindAll(t => 
                (userId != null ? ((t.UserId == userId) || (t.UsersByTicket.FirstOrDefault(u => u.Id == userId) != null)) : true)  &&
                ( t.TicketDate.CompareTo(init.AddMinutes(-1)) >= 0 && t.TicketDate.CompareTo(end.AddMinutes(1)) <= 0 ) &&
                (   ( t.Issues.Name.ToUpper().Contains(searchPhrase.ToUpper()) ) ||
                    (t.Details.ToUpper().Contains(searchPhrase.ToUpper()) ) ||
                    (t.Issues.ParentIssue != null && t.Issues.ParentIssue.Name.ToUpper().Contains(searchPhrase.ToUpper()) ) ||
                    (userId != null ? false : t.AspNetUsers.UserName.ToUpper().Contains(searchPhrase.ToUpper())) ||
                    (t.Id.ToString().Contains(searchPhrase))
                ));
            // Filtro estatus del ticket
            switch(status)
            {
                case "Abiertos":
                    ticketsFound = ticketsFound.FindAll(t => !t.Closed);
                    break;
                case "Cerrados":
                    ticketsFound = ticketsFound.FindAll(t => t.Closed);
                    break;
            }
            // Total elements
            total = ticketsFound.Count;
            // Page number and elements number
            ticketsFound = ticketsFound.GetRange(
                (current - 1) * rowCount, 
                (((current - 1) * rowCount) + rowCount) <= total ? rowCount : total - ((current - 1) * rowCount));
            return ticketsFound;
        }

        /// <summary>
        /// Devuelve una lista con todos los ticket que un usuario administra
        /// </summary>
        /// <param name="areasByUser">Áreas que el usuarios administra ej: RTIC, Admnistración</param>
        /// <param name="current">Página actual en el grid</param>
        /// <param name="rowCount">Número de elementos por página</param>
        /// <param name="sort">Parámetros de ordenamiento</param>
        /// <param name="searchPhrase">Palabra clave (Busqueda)</param>
        /// <param name="init">Fecha inicial (Rango de fecha)</param>
        /// <param name="end">Fecha final (Rango de fecha)</param>
        /// <param name="status">Estatus de los tickets a obtener</param>
        /// <param name="total">Número de tickets encontrados</param>
        /// <returns></returns>
        public List<TicketsDAL> GetTicketsAdmin(List<string> areasByUser, int current, int rowCount, Dictionary<object, string> sort, string searchPhrase, DateTime init, DateTime end, string status, ref int total)
        {
            List<TicketsDAL> ticketsFound;
            string regexAreas = "^" + String.Join("|", areasByUser) + "$";

            switch (sort.Keys.First().ToString())
            {
                case "Id":
                    ticketsFound = sort["Id"] == "asc" ?
                        Value.OrderBy(a => a.Id).ToList() :
                        Value.OrderBy(a => a.Id).Reverse().ToList();
                    break;
                case "issue":
                    ticketsFound = sort["issue"] == "asc" ?
                        Value.OrderBy(a => a.Issues.Name).ToList() :
                        Value.OrderBy(a => a.Issues.Name).Reverse().ToList();
                    break;
                case "ticketSubject":
                    ticketsFound = sort["ticketSubject"] == "asc" ?
                        Value.OrderBy(a => a.TicketSubject).ToList() :
                        Value.OrderBy(a => a.TicketSubject).Reverse().ToList();
                    break;
                case "userName":
                    ticketsFound = sort["userName"] == "asc" ?
                        Value.OrderBy(a => a.AspNetUsers.UserName).ToList() :
                        Value.OrderBy(a => a.AspNetUsers.UserName).Reverse().ToList();
                    break;
                case "ticketDate":
                    ticketsFound = sort["ticketDate"] == "asc" ?
                        Value.OrderBy(a => a.TicketDate).ToList() :
                        Value.OrderBy(a => a.TicketDate).Reverse().ToList();
                    break;
                case "area":
                    ticketsFound = sort["area"] == "asc" ?
                        Value.OrderBy(a => a.Issues.ParentIssue.Name).ToList() :
                        Value.OrderBy(a => a.Issues.ParentIssue.Name).Reverse().ToList();
                    break;
                default:
                    ticketsFound = Value.OrderBy(a => a.TicketDate).Reverse().ToList();;
                    break;

            }
            // Filtro mediante el rango de fechas y la palbra de busqueda
            ticketsFound = ticketsFound.FindAll(t =>
                (t.TicketDate.CompareTo(init.AddMinutes(-1)) >= 0 && t.TicketDate.CompareTo(end.AddMinutes(1)) <= 0) &&
                (t.Issues.ParentIssue != null && Regex.IsMatch(t.Issues.ParentIssue.Name, regexAreas)) &&
                (   (t.Issues.Name.ToUpper().Contains(searchPhrase.ToUpper())) ||
                    (t.Details.ToUpper().Contains(searchPhrase.ToUpper())) ||
                    (t.AspNetUsers.UserName.ToUpper().Contains(searchPhrase.ToUpper())) ||
                    (t.Id.ToString().Contains(searchPhrase))
                ));
            // Filtro estatus del ticket
            switch (status)
            {
                case "Abiertos":
                    ticketsFound = ticketsFound.FindAll(t => !t.Closed);
                    break;
                case "Cerrados":
                    ticketsFound = ticketsFound.FindAll(t => t.Closed);
                    break;
            }
            // Total de elementos
            total = ticketsFound.Count;
            // Filtro mediante el número de página y los elementos por página
            ticketsFound = ticketsFound.GetRange(
                (current - 1) * rowCount,
                (((current - 1) * rowCount) + rowCount) <= total ? rowCount : total - ((current - 1) * rowCount));
            return ticketsFound;
        }

        /// <summary>
        /// Cambia el estado de un ticket a cerrado
        /// </summary>
        /// <param name="ticketId">Id del ticket</param>
        /// <returns></returns>
        public bool closeTicket(long ticketId)
        {
            // Ticket a modificar
            TicketsDAL ticket = Value.FirstOrDefault(a => a.Id == ticketId);
            try
            {
                ticket.Closed = true; // Estado = cerrado
                ticket.TicketStatus = "Ticket cerrado"; // Estatus del ticket
                db.SaveChanges();
            }
            catch (Exception) { return false; }
            return true;
        }

        /// <summary>
        /// Cambia el estado de un ticket a abierto
        /// </summary>
        /// <param name="ticketId">Id del ticket</param>
        /// <returns></returns>
        public bool OpenTicket(long ticketId)
        {
            // Ticket a modificar
            TicketsDAL ticket = Value.FirstOrDefault(a => a.Id == ticketId);
            try
            {
                ticket.Closed = false; // Estado = abierto
                ticket.TicketStatus = "Ticket reabierto"; // Estatus del ticket
                db.SaveChanges();
            }
            catch (Exception) { return false; }
            return true;
        }

        /// <summary>
        /// Devuelve una lista con todos los archivos adjuntos de un ticket y modifica la variable total con el número de elementos encontrados.
        /// NOTA: Utilizar para devolver información a un Grid "BootGrid". Ver View "Tickets/Attachments"
        /// </summary>
        /// <param name="current">Número de página</param>
        /// <param name="rowCount">Elementos por página</param>
        /// <param name="sort">Parámetros de ordenación</param>
        /// <param name="searchPhrase">Palabra clave (Busqueda)</param>
        /// <param name="idTicket">Id del ticket</param>
        /// <param name="total">Total de elementos encontrados</param>
        /// <returns></returns>
        public List<AttachmentsDAL> GetTicketAttachments(int current, int rowCount, Dictionary<object, string> sort, string searchPhrase, long idTicket, ref int total)
        {
            List<AttachmentsDAL> ticketAttachments;
            AttachmentsBLL attachments = new AttachmentsBLL();

            ticketAttachments = attachments.GetTicketAttachments(idTicket);

            // Ordenación mediante el diccionario proveniente del BootGrid
            switch (sort.Keys.First().ToString())
            {
                case "Options": // Mediante el nombre de archivo
                    ticketAttachments = sort["Options"] == "asc" ?
                        ticketAttachments.OrderBy(a => a.Name).ToList() :
                        ticketAttachments.OrderBy(a => a.Name).Reverse().ToList();
                    break;
                case "FileName": // Mediante el nombre de archivo
                    ticketAttachments = sort["FileName"] == "asc" ?
                        ticketAttachments.OrderBy(a => a.Name).ToList() :
                        ticketAttachments.OrderBy(a => a.Name).Reverse().ToList();
                    break;
                case "FileSize": // Mediante el tamaño del archivo
                    ticketAttachments = sort["FileSize"] == "asc" ?
                        ticketAttachments.OrderBy(a => a.FileSize).ToList() :
                        ticketAttachments.OrderBy(a => a.FileSize).Reverse().ToList();
                    break;
                case "UserName": // Mediante nombre de usuario
                    ticketAttachments = sort["UserName"] == "asc" ?
                        ticketAttachments.OrderBy(a => a.AspNetUsers.UserName).ToList() :
                        ticketAttachments.OrderBy(a => a.AspNetUsers.UserName).Reverse().ToList();
                    break;
                case "CreationDate": // Mediante la fecha de creación
                    ticketAttachments = sort["CreationDate"] == "asc" ?
                        ticketAttachments.OrderBy(a => a.CreationDate).ToList() :
                        ticketAttachments.OrderBy(a => a.CreationDate).Reverse().ToList();
                    break;
                default: // Mediante la fecha de creación, del más nuevo al más viejo
                    ticketAttachments = ticketAttachments.OrderBy(a => a.CreationDate).Reverse().ToList(); ;
                    break;

            }

            // Número de elementos encontrados
            total = ticketAttachments.Count;

            return ticketAttachments;
        }

        /// <summary>
        /// Agrega un usuario como colaborador a un ticket
        /// </summary>
        /// <param name="ticketId">Id del ticket</param>
        /// <param name="userId">Id del usuario a agregar</param>
        /// <returns></returns>
        public bool AddUser(long ticketId, string userId)
        {
            // Busqueda del ticket especificado
            TicketsDAL ticket = Value.FirstOrDefault(t => t.Id == ticketId);
            // Busqueda del usuario
            AspNetUsersDAL user = db.AspNetUsers.FirstOrDefault(u => u.Id == userId);

            using (AspNetUsersBLL userBLL = new AspNetUsersBLL(userId))
            {
                // Validación de existencia de usuario y ticket
                if ((ticket != null) && userBLL.Exist() && (ticket.UserId != userId) &&
                    (ticket.UsersByTicket.FirstOrDefault(u => u.Id == userId) == null))
                {
                    try
                    {
                        // Si el usuario no cuenta con el perfil necesario para consultar tickets se le asigna
                        if (!userBLL.isInRole("Services"))
                        {
                            if (userBLL.SetRole("Services"))
                            {
                                // Asignación del usuario al ticket
                                ticket.UsersByTicket.Add(user);
                                db.SaveChanges();
                                return true; // Usuario asignado correctamente al ticket
                            }
                            else
                                return false;
                        }
                        else
                        {
                            // Asignación del usuario al ticket
                            ticket.UsersByTicket.Add(user);
                            db.SaveChanges();
                            return true; // Usuario asignado correctamente al ticket
                        }
                    }
                    catch (Exception) { return false; } // Usuario no asignado
                }
                else
                    return false; // Usuario no asignado
            }
        }

        public bool RemoveUser(long ticketId, string userId)
        {
            // Busqueda del ticket especificado
            TicketsDAL ticket = Value.FirstOrDefault(t => t.Id == ticketId);
            // Busqueda del usuario
            AspNetUsersDAL user = db.AspNetUsers.FirstOrDefault(u => u.Id == userId);

            // Validación de existencia de usuario y ticket
            if ((ticket != null) && (user != null) &&
                (ticket.UsersByTicket.FirstOrDefault(u => u.Id == userId) != null))
            {
                try
                {
                    // Eliminación del usuario
                    ticket.UsersByTicket.Remove(user);
                    db.SaveChanges();
                    return true; // Usuario eliminado correctamente al ticket
                }
                catch (Exception) { return false; } // Usuario no eliminado
            }
            else
                return false; // Usuario no eliminado
        }

        /// <summary>
        /// Devuelve todos los usuarios que colaboran en el ticket. 
        /// Implementar en Bootstrap BootGrid
        /// </summary>
        /// <param name="current">Página actual</param>
        /// <param name="rowCount">Número de elementos por página</param>
        /// <param name="sort">Parámetros de ordenamiento</param>
        /// <param name="searchPhrase">Palabra clave (Busqueda)</param>
        /// <param name="idTicket">Id del ticket a utilizar</param>
        /// <returns></returns>
        public GridTicketUsersModel GetTicketUsers(int current, int rowCount, Dictionary<object, string> sort, string searchPhrase, long idTicket)
        {
            List<AspNetUsersDAL> ticketUsers;
            // Busqueda del ticket
            TicketsDAL ticket = Value.FirstOrDefault(t => t.Id == idTicket);
            GridTicketUsersModel gridData; // Variable a devolver al grid

            ticketUsers = ticket.UsersByTicket.ToList();
            // Ordenación mediante el diccionario proveniente del BootGrid
            switch (sort.Keys.First().ToString())
            {
                case "UPN": // Mediante el UPN (Id) de usuario
                    ticketUsers = sort["UPN"] == "asc" ?
                        ticketUsers.OrderBy(a => a.Id).ToList() :
                        ticketUsers.OrderBy(a => a.Id).Reverse().ToList();
                    break;
                case "Name": // Mediante el nombre de usuario
                    ticketUsers = sort["Name"] == "asc" ?
                        ticketUsers.OrderBy(a => a.UserName).ToList() :
                        ticketUsers.OrderBy(a => a.UserName).Reverse().ToList();
                    break;
                case "Options": // Mediante el nombre de usuario
                    ticketUsers = sort["Options"] == "asc" ?
                        ticketUsers.OrderBy(a => a.Id).ToList() :
                        ticketUsers.OrderBy(a => a.Id).Reverse().ToList();
                    break;
            }

            // Inicialización
            gridData = new GridTicketUsersModel()
            {
                current = current,
                rowCount = rowCount,
                total = ticketUsers.Count
            };
            // Llenado de registros de usuarios
            foreach (AspNetUsersDAL user in ticketUsers)
            {
                gridData.rows.Add(new TicketsUsersViewModel()
                {
                    UPN = user.Id,
                    Name = user.UserName
                });
            }
            return gridData;
        }

        /// <summary>
        /// Devuelve una lista con los emails de usuarios en un ticket
        /// </summary>
        /// <param name="idTicket">Id de ticket a buscar</param>
        /// <returns></returns>
        public List<string> GetUsersEmails(long idTicket)
        {
            // Busqueda del ticket
            TicketsDAL ticket = Value.FirstOrDefault(t => t.Id == idTicket); 
            // Busqueda de usuarios colaboradores y administradores
            List<string> ticketUsers = (from u in ticket.UsersByTicket
                          select u.Email).Union( // Union de usuarios colaboradores con usuarios administradores
                            from a in db.AspNetUsers
                            where (a.AspNetRoles.FirstOrDefault(r => r.IssueArea == ticket.Issues.ParentIssue.Name) != null) ||
                                  (a.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == ticket.Issues.ParentIssue.Name) != null)
                            select a.Email
                          ).ToList();
            // Adición de usuario creador del ticket
            ticketUsers.Add(ticket.AspNetUsers.Email);
            return ticketUsers;
        }

        // <summary>
        /// Devuelve una lista con los ID'S de usuarios en un ticket
        /// </summary>
        /// <param name="idTicket">Id de ticket a buscar</param>
        /// <returns></returns>
        public List<string> GetUsersId(long idTicket)
        {
            // Busqueda del ticket
            TicketsDAL ticket = Value.FirstOrDefault(t => t.Id == idTicket);
            // Busqueda de usuarios colaboradores y administradores
            List<string> ticketUsers = (from u in ticket.UsersByTicket
                                        select u.Id).Union( // Union de usuarios colaboradores con usuarios administradores
                            from a in db.AspNetUsers
                            where (a.AspNetRoles.FirstOrDefault(r => r.IssueArea == ticket.Issues.ParentIssue.Name) != null) ||
                                  (a.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == ticket.Issues.ParentIssue.Name) != null)
                            select a.Id
                          ).ToList();
            // Adición de usuario creador del ticket
            ticketUsers.Add(ticket.AspNetUsers.Id);
            return ticketUsers;
        }

        /// <summary>
        /// Devuelve una lista con los emails de todos los administradores para un ticket determinado por una área
        /// </summary>
        /// <param name="areaId">ID de área</param>
        /// <returns></returns>
        public List<string> GetAdminsEmails(long areaId)
        {
            IssuesDAL issue = db.Issues.FirstOrDefault(i => i.Id == areaId);
            string area = issue != null ? issue.Name : string.Empty;

            List<string> users = (from u in db.AspNetUsers
                                  where (u.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null) ||
                                        (u.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null)
                                  select u.Email
                                ).ToList();
            return users;
        }

        /// <summary>
        /// Devuelve una lista con los emails de todos los administradores para un ticket determinado por una área
        /// </summary>
        /// <param name="area">Nombre de área</param>
        /// <returns></returns>
        public List<string> GetAdminsEmails(string area)
        {
            List<string> users = (from u in db.AspNetUsers
                                  where (u.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null) ||
                                        (u.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null)
                                  select u.Email
                                ).ToList();
            return users;
        }

        /// <summary>
        /// Devuelve una lista con los ID'S de todos los administradores para un ticket determinado por una área
        /// </summary>
        /// <param name="areaId">ID de área</param>
        /// <returns></returns>
        public List<string> GetAdminsId(long areaId)
        {
            IssuesDAL issue = db.Issues.FirstOrDefault(i => i.Id == areaId);
            string area = issue != null ? issue.Name : string.Empty;

            List<string> users = (from u in db.AspNetUsers
                                  where (u.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null) ||
                                        (u.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null)
                                  select u.Id
                                ).ToList();
            return users;
        }

        /// <summary>
        /// Devuelve una lista con los ID'S de todos los administradores para un ticket determinado por una área
        /// </summary>
        /// <param name="area">Nombre de área</param>
        /// <returns></returns>
        public List<string> GetAdminsId(string area)
        {
            List<string> users = (from u in db.AspNetUsers
                                  where (u.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null) ||
                                        (u.AspNetProfiles.AspNetRoles.FirstOrDefault(r => r.IssueArea == area) != null)
                                  select u.Id
                                ).ToList();
            return users;
        }

        public void Dispose()
        {
            db.Dispose();
        }

    }
}